//---------------------------------------------------------------------
// <copyright file="UpdateCommand.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------

using System.Data.Metadata.Edm;
using System.Data.Common;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Data.Common.Utils;
using System.Data.Common.CommandTrees;
using System.Data.Objects;
using System.Linq;
using System.Data.EntityClient;
using System.Threading;
namespace System.Data.Mapping.Update.Internal
{
    internal enum UpdateCommandKind
    {
        Dynamic,
        Function,
    }

    /// <summary>
    /// Class storing the result of compiling an instance DML command.
    /// </summary>
    internal abstract class UpdateCommand : IComparable<UpdateCommand>, IEquatable<UpdateCommand>
    {
        protected UpdateCommand(PropagatorResult originalValues, PropagatorResult currentValues)
        {
            m_originalValues = originalValues;
            m_currentValues = currentValues;
        }

        private readonly PropagatorResult m_originalValues;
        private readonly PropagatorResult m_currentValues;

        // When it is not possible to order two commands based on their contents, we assign an 'ordering identifier'
        // so that one will consistently precede the other.
        private static int s_orderingIdentifierCounter;
        private int m_orderingIdentifier;

        /// <summary>
        /// Gets all identifiers (key values basically) generated by this command. For instance,
        /// @@IDENTITY values.
        /// </summary>
        internal abstract IEnumerable<int> OutputIdentifiers { get; }

        /// <summary>
        /// Gets all identifiers required by this command.
        /// </summary>
        internal abstract IEnumerable<int> InputIdentifiers { get; }

        /// <summary>
        /// Gets table (if any) associated with the current command. FunctionUpdateCommand has no table.
        /// </summary>
        internal virtual EntitySet Table
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// Gets type of command.
        /// </summary>
        internal abstract UpdateCommandKind Kind
        {
            get;
        }

        /// <summary>
        /// Gets original values of row/entity handled by this command.
        /// </summary>
        internal PropagatorResult OriginalValues { get { return m_originalValues; } }

        /// <summary>
        /// Gets current values of row/entity handled by this command.
        /// </summary>
        internal PropagatorResult CurrentValues { get { return m_currentValues; } }

        /// <summary>
        /// Yields all state entries contributing to this command. Used for error reporting.
        /// </summary>
        /// <param name="translator">Translator context.</param>
        /// <returns>Related state entries.</returns>
        internal abstract IList<IEntityStateEntry> GetStateEntries(UpdateTranslator translator);

        /// <summary>
        /// Determines model level dependencies for the current command. Dependencies are based
        /// on the model operations performed by the command (adding or deleting entities or relationships).
        /// </summary>
        internal void GetRequiredAndProducedEntities(UpdateTranslator translator,
            KeyToListMap<EntityKey, UpdateCommand> addedEntities,
            KeyToListMap<EntityKey, UpdateCommand> deletedEntities,
            KeyToListMap<EntityKey, UpdateCommand> addedRelationships,
            KeyToListMap<EntityKey, UpdateCommand> deletedRelationships)
        {
            IList<IEntityStateEntry> stateEntries = GetStateEntries(translator);

            foreach (IEntityStateEntry stateEntry in stateEntries)
            {
                if (!stateEntry.IsRelationship)
                {
                    if (stateEntry.State == EntityState.Added)
                    {
                        addedEntities.Add(stateEntry.EntityKey, this);
                    }
                    else if (stateEntry.State == EntityState.Deleted)
                    {
                        deletedEntities.Add(stateEntry.EntityKey, this);
                    }
                }
            }

            // process foreign keys
            if (null != this.OriginalValues)
            {
                // if a foreign key being deleted, it 'frees' or 'produces' the referenced key
                AddReferencedEntities(translator, this.OriginalValues, deletedRelationships);
            }
            if (null != this.CurrentValues)
            {
                // if a foreign key is being added, if requires the referenced key
                AddReferencedEntities(translator, this.CurrentValues, addedRelationships);
            }

            // process relationships
            foreach (IEntityStateEntry stateEntry in stateEntries)
            {
                if (stateEntry.IsRelationship)
                {
                    // only worry about the relationship if it is being added or deleted
                    bool isAdded = stateEntry.State == EntityState.Added;
                    if (isAdded || stateEntry.State == EntityState.Deleted)
                    {
                        DbDataRecord record = isAdded ? (DbDataRecord)stateEntry.CurrentValues : stateEntry.OriginalValues;
                        Debug.Assert(2 == record.FieldCount, "non-binary relationship?");
                        EntityKey end1 = (EntityKey)record[0];
                        EntityKey end2 = (EntityKey)record[1];

                        // relationships require the entity when they're added and free the entity when they're deleted...
                        KeyToListMap<EntityKey, UpdateCommand> affected = isAdded ? addedRelationships : deletedRelationships;

                        // both ends are being modified by the relationship
                        affected.Add(end1, this);
                        affected.Add(end2, this);
                    }
                }
            }
        }

        private void AddReferencedEntities(UpdateTranslator translator, PropagatorResult result, KeyToListMap<EntityKey, UpdateCommand> referencedEntities)
        {
            foreach (PropagatorResult property in result.GetMemberValues())
            {
                if (property.IsSimple && property.Identifier != PropagatorResult.NullIdentifier &&
                    (PropagatorFlags.ForeignKey == (property.PropagatorFlags & PropagatorFlags.ForeignKey)))
                {
                    foreach (int principal in translator.KeyManager.GetDirectReferences(property.Identifier))
                    {
                        PropagatorResult owner;
                        if (translator.KeyManager.TryGetIdentifierOwner(principal, out owner) &&
                            null != owner.StateEntry)
                        {
                            Debug.Assert(!owner.StateEntry.IsRelationship, "owner must not be a relationship");
                            referencedEntities.Add(owner.StateEntry.EntityKey, this);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Executes the current update command.
        /// </summary>
        /// <param name="translator">Translator context.</param>
        /// <param name="connection">EntityConnection to use (and implicitly, the EntityTransaction to use).</param>
        /// <param name="identifierValues">Aggregator for identifier values (read for InputIdentifiers; write for
        /// OutputIdentifiers</param>
        /// <param name="generatedValues">Aggregator for server generated values.</param>
        /// <returns>Number of rows affected by the command.</returns>
        internal abstract long Execute(UpdateTranslator translator, EntityConnection connection, Dictionary<int, object> identifierValues,
            List<KeyValuePair<PropagatorResult, object>> generatedValues);

        /// <summary>
        /// Implementation of CompareTo for concrete subclass of UpdateCommand.
        /// </summary>
        internal abstract int CompareToType(UpdateCommand other);

        /// <summary>
        /// Provides a suggested ordering between two commands. Ensuring a consistent ordering is important to avoid deadlocks
        /// between two clients because it means locks are acquired in the same order where possible. The ordering criteria are as 
        /// follows (and are partly implemented in the CompareToType method). In some cases there are specific secondary
        /// reasons for the order (e.g. operator kind), but for the most case we just care that a consistent ordering
        /// is applied:
        /// 
        /// - The kind of command (dynamic or function). This is an arbitrary criteria.
        /// - The kind of operator (insert, update, delete). See <see cref="ModificationOperator"/> for details of the ordering.
        /// - The target of the modification (table for dynamic, set for function).
        /// - Primary key for the modification (table key for dynamic, entity keys for function).
        /// 
        /// If it is not possible to differentiate between two commands (e.g., where the user is inserting entities with server-generated
        /// primary keys and has not given explicit values), arbitrary ordering identifiers are assigned to the commands to
        /// ensure CompareTo is well-behaved (doesn't return 0 for different commands and suggests consistent ordering).
        /// </summary>
        public int CompareTo(UpdateCommand other)
        {
            // If the commands are the same (by reference), return 0 immediately. Otherwise, we try to find (and eventually
            // force) an ordering between them by returning a value that is non-zero.
            if (this.Equals(other)) { return 0; }
            Debug.Assert(null != other, "comparing to null UpdateCommand");
            int result = (int)this.Kind - (int)other.Kind;
            if (0 != result) { return result; }

            // defer to specific type for other comparisons...
            result = CompareToType(other);
            if (0 != result) { return result; }

            // if the commands are indistinguishable, assign arbitrary identifiers to them to ensure consistent ordering
            unchecked
            {
                if (this.m_orderingIdentifier == 0)
                {
                    this.m_orderingIdentifier = Interlocked.Increment(ref s_orderingIdentifierCounter);
                }
                if (other.m_orderingIdentifier == 0)
                {
                    other.m_orderingIdentifier = Interlocked.Increment(ref s_orderingIdentifierCounter);
                }

                return this.m_orderingIdentifier - other.m_orderingIdentifier;
            }
        }

        #region IEquatable: note that we use reference equality
        public bool Equals(UpdateCommand other)
        {
            return base.Equals(other);
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        #endregion
    }
}
